iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0

在完成全系統錯誤處理與 Log 監控模組 (ErrorReportingService.gs) 後,我們的 NGO 房間預約系統已具備極高的強韌性與自我診斷能力。

今天我們將進入 模組 F:系統整合與運營維護 的核心環節——資料庫欄位結構自動校驗、Google Sheets 定時備份機制 以及 端到端(End-to-End, E2E)整合測試。這將確保系統在長期運作下,資料庫結構不因人為誤操作而損壞,並具備災害復原(Disaster Recovery)與全流程驗證的能力。


1. 資料一致性與自動備份架構

為確保 NGO 在無專職 IT 人員維運的情況下系統仍能長期穩定運行,我們設計了以下自動化維護流程:

                              [ 定時觸發器 (Time-driven) ]
                                            │
                      ┌─────────────────────┴─────────────────────┐
                      ▼                                           ▼
          【1. 資料庫欄位結構自動校驗】                 【2. 數據定時自動備份】
       (Check & Repair Sheets/Headers)             (Backup to Google Drive Folder)
                      │                                           │
                      └─────────────────────┬─────────────────────┘
                                            ▼
                               【3. 端到端 E2E 整合測試】
                          (模擬 預約 -> 簽到 -> 取消 流程)

系統設計重點:

  1. 自動修復(Self-Healing Header):系統啟動或排程執行時,自動檢查 Bookings 與 SystemLogs 工作表是否存在;若缺少關鍵欄位,自動補充並格式化。
  2. 無痛備份(Zero-Downtime Backup):定期將目前的 Google Sheets 複製備份至指定的 Google Drive 資料夾,並以日期時間命名。
  3. 端到端測試(E2E Test Suite):提供一鍵測試函式,自動模擬預約建立、時段排查、簽到以及規章取消的全套業務流程。

2. 後端 MaintenanceService 完整程式碼 (MaintenanceService.gs)

請在 Apps Script 專案中新增名為 MaintenanceService.gs 的檔案,寫入以下程式碼:

/**
 * ==============================================================================
 * 系統整合測試、資料一致性與備份機制 (MaintenanceService.gs)
 * 專案:NGO 房間與資源預約管理系統 (Build on Google AI Track)
 * 說明:提供資料庫欄位結構自動校驗、Google Sheets 定時備份與端到端 E2E 測試。
 * ==============================================================================
 */

/**
 * 1. 資料庫欄位結構自動校驗與自動修復 (Self-Healing)
 */
function verifyAndRepairDatabaseStructure() {
  const config = getConfig();
  const ss = SpreadsheetApp.openById(config.SPREADSHEET_ID);
  
  // 定義標準工作表結構需求
  const schemaRequirements = {
    "Bookings": [
      "BookingID", "UserEmail", "RoomID", "StartTime", "EndTime", 
      "Status", "CheckInTime", "ReleaseTime", "AdminNote", "CreatedAt"
    ],
    "SystemLogs": [
      "Timestamp", "Level", "Function", "Message", "ContextData", "StackTrace"
    ]
  };

  let repairCount = 0;

  for (const [sheetName, requiredHeaders] of Object.entries(schemaRequirements)) {
    let sheet = ss.getSheetByName(sheetName);

    // 若工作表不存在則創建
    if (!sheet) {
      sheet = ss.insertSheet(sheetName);
      sheet.appendRow(requiredHeaders);
      sheet.getRange(1, 1, 1, requiredHeaders.length).setFontWeight("bold").setBackground("#f1f5f9");
      sheet.setFrozenRows(1);
      Logger.log(`🛠️ [Auto-Repair] 已自動創建缺少的工作表: ${sheetName}`);
      repairCount++;
      continue;
    }

    // 檢查第一行表頭欄位
    const currentHeaders = sheet.getRange(1, 1, 1, Math.max(sheet.getLastColumn(), 1)).getValues()[0];
    const missingHeaders = requiredHeaders.filter(h => !currentHeaders.includes(h));

    if (missingHeaders.length > 0) {
      // 補齊缺少的欄位
      missingHeaders.forEach(header => {
        const nextCol = sheet.getLastColumn() + 1;
        sheet.getRange(1, nextCol).setValue(header).setFontWeight("bold").setBackground("#f1f5f9");
        Logger.log(`🛠️ [Auto-Repair] 工作表 ${sheetName} 已補齊缺少欄位: ${header}`);
      });
      repairCount++;
    }
  }

  Logger.log(`✅ [Database Check] 校驗完成,共執行 ${repairCount} 項結構修復。`);
  return { success: true, repairedCount: repairCount };
}

/**
 * 2. Google Sheets 資料庫自動備份至 Google Drive
 * 建議設定 Time-driven Trigger 每週或每日深夜執行
 */
function backupDatabaseToDrive() {
  try {
    const config = getConfig();
    const sourceFile = DriveApp.getFileById(config.SPREADSHEET_ID);
    
    // 取得或建立備份資料夾
    const folderName = "NGO_Booking_System_Backups";
    const folders = DriveApp.getFoldersByName(folderName);
    let targetFolder;

    if (folders.hasNext()) {
      targetFolder = folders.next();
    } else {
      targetFolder = DriveApp.createFolder(folderName);
      Logger.log(`📁 已在 Google Drive 建立備份資料夾: ${folderName}`);
    }

    // 格式化備份檔名: NGO_Bookings_Backup_YYYYMMDD_HHMM.xlsx
    const timeStampStr = Utilities.formatDate(new Date(), "GMT+8", "yyyyMMdd_HHmm");
    const backupFileName = `NGO_Bookings_Backup_${timeStampStr}`;

    // 執行複製備份
    const backupCopy = sourceFile.makeCopy(backupFileName, targetFolder);

    Logger.log(`🎉 [Backup Success] 已成功備份至 Drive 資料夾: ${backupFileName} (ID: ${backupCopy.getId()})`);
    return { success: true, backupFileId: backupCopy.getId(), fileName: backupFileName };

  } catch (error) {
    logAndReportError(error, "backupDatabaseToDrive", {}, LOG_LEVEL.CRITICAL);
    return { success: false, message: error.toString() };
  }
}

/**
 * 3. 端到端 (E2E) 流程整合自動化測試
 * 模擬全套業務流程:預約建立 -> 時段排查 -> 簽到測試 -> 預約取消規章
 */
function runEndToEndSystemTest() {
  Logger.log("=== 🚀 開始執行端到端 (E2E) 系統整合測試 ===");
  
  const testEmail = "test_runner@ngo.org";
  const testRoom = "A1";
  const now = new Date();
  
  // 設定測試時間:明天 14:00 - 15:00
  const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
  const startIso = new Date(tomorrow.setHours(14, 0, 0, 0)).toISOString();
  const endIso = new Date(tomorrow.setHours(15, 0, 0, 0)).toISOString();
  const dateStr = startIso.split("T")[0];

  // 測試步驟 1:結構校驗
  Logger.log("👉 [Step 1] 執行資料庫結構校驗...");
  verifyAndRepairDatabaseStructure();

  // 測試步驟 2:提交新預約
  Logger.log("👉 [Step 2] 測試提交新預約 API...");
  const bookingResult = submitNewBooking(testEmail, testRoom, startIso, endIso, "E2E 自動化測試預約");
  Logger.log(`預約結果: ${JSON.stringify(bookingResult)}`);
  
  if (!bookingResult.success) {
    Logger.log("❌ E2E 測試於 Step 2 失敗。");
    return;
  }
  const testBookingId = bookingResult.bookingId;

  // 測試步驟 3:查詢可用時段 (應顯示該時段不可用)
  Logger.log("👉 [Step 3] 測試時段重複排查 API...");
  const slotsResult = getAvailableSlots(dateStr, testRoom);
  Logger.log(`時段查詢結果成功取得 ${slotsResult.slots.length} 個 Slots。`);

  // 測試步驟 4:測試現場簽到機制
  Logger.log("👉 [Step 4] 測試現場簽到 API...");
  const checkInResult = processCheckIn(testBookingId);
  Logger.log(`簽到結果: ${JSON.stringify(checkInResult)}`);

  // 測試步驟 5:測試 24HR 規章取消機制
  Logger.log("👉 [Step 5] 測試取消與規章 API...");
  const cancelResult = cancelBookingWithPolicy(testBookingId, "E2E 測試流程清理", testEmail);
  Logger.log(`取消結果: ${JSON.stringify(cancelResult)}`);

  Logger.log("=== 🎉 端到端 (E2E) 系統整合測試全數通過! ===");
}


3. 設定定時自動備份與校驗觸發器 (Triggers)

要讓系統實現真正的無人值守維運,可在 Google Apps Script 貼上以下觸發器建立腳本(或手動於「觸發條件」介面設定):

/**
 * 自動建立系統定時維護與備份觸發器 (只需執行一次)
 */
function setupMaintenanceTriggers() {
  // 刪除舊有的同名觸發器避免重複
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === "backupDatabaseToDrive" || 
        trigger.getHandlerFunction() === "verifyAndRepairDatabaseStructure") {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  // 1. 設定每日凌晨 02:00 執行資料庫備份
  ScriptApp.newTrigger("backupDatabaseToDrive")
    .timeBased()
    .everyDays(1)
    .atHour(2)
    .create();

  // 2. 設定每週一凌晨 03:00 執行結構校驗
  ScriptApp.newTrigger("verifyAndRepairDatabaseStructure")
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(3)
    .create();

  Logger.log("✅ 系統定時維護與備份觸發器設定完成!");
}


4. 小結

今天我們完成了 ** 系統整合測試、資料一致性與備份機制 (MaintenanceService.gs)**。透過欄位自動修復(Self-Healing)、無痛 Drive 數據備份與全自動 E2E 測試套件,為這套 NGO 預約系統鋪平了長期營運與維護的道路。

明天繼續……


上一篇
【Day 24】全系統錯誤處理、例外監控與 Log 日誌紀錄
下一篇
【Day 26】系統驗收、用戶權限劃分與測試報告
系列文
零預算 NGO 數位轉型挑戰:30 天打造智慧訂房系統 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言